package dslab.nameserver;

import java.rmi.RemoteException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * an INameserverRemote implementation that implements all RMI functionality
 * required by users of nameservers
 */
public class NameserverRemote implements INameserverRemote {

    private final String componentId;
    private final Map<String, INameserverRemote> nameservers;
    private final Map<String, String> addresses;
    private final INameserverRemote root_stub;

    /**
     * NameserverRemote constructor
     * @param componentId string representation of component
     * @param root_stub an INameserverRemote object referring to the root nameserver stub
     */
    public NameserverRemote(String componentId, INameserverRemote root_stub) {
        this.componentId = componentId;
        this.nameservers = new ConcurrentHashMap<>();
        this.addresses = new ConcurrentHashMap<>();
        // root_stub == null  ==>  this object is root stub
        this.root_stub = root_stub;
    }

    /**
     * getter for the addresses datatype
     * @return a map of all locations stored on this nameserver
     */
    public Map<String, String> getAddresses() {
        return addresses;
    }

    /**
     * getter for the nameservers datatype
     * @return a map of all nameserver stubs stored on this nameserver
     */
    public Map<String, INameserverRemote> getNameservers() {
        return nameservers;
    }

    @Override
    public void registerNameserver(String domain, INameserverRemote nameserver) throws RemoteException,
            AlreadyRegisteredException, InvalidDomainException {
        System.out.printf("@%s: registerNameserver called with domain %s%n", componentId, domain);
        // in case of root level nameserver
        if (!domain.contains(".")) {
            System.out.printf("@%s: adding root level nameserver with domain %s%n", componentId, domain);

            INameserverRemote result = nameservers.putIfAbsent(domain, nameserver);

            // do not allow re-registration of nameserver
            if (result != null) {
                System.err.printf(
                        "@%s: error: cannot register nameserver, \"%s\" is already registered%n",
                        componentId,
                        domain
                );
                throw new AlreadyRegisteredException(String.format(
                        "cannot register nameserver, \"%s\" is already registered",
                        domain
                ));
            }
        // in case of nameserver that control a zone within that of another root level nameserver
        } else {
            String[] zones = domain.split("\\.");
            String rootLevelZone = zones[zones.length - 1];
            String zonesWithoutRoot = domain.substring(0, domain.length() - rootLevelZone.length() - 1);
            System.out.printf("@%s: contacting nameserver with domain %s%n", componentId, rootLevelZone);

            // attempt registering nameserver recursively
            INameserverRemote next = nameservers.get(rootLevelZone);
            if (next == null) {
                System.err.printf(
                    "@%s: error: cannot register nameserver, next nameserver hop \"%s\" not known%n",
                    componentId,
                    domain
                );
                throw new InvalidDomainException(String.format(
                    "cannot register nameserver, next nameserver hop \"%s\" not known",
                    zonesWithoutRoot
                ));
            }

            // make recursive call
            next.registerNameserver(zonesWithoutRoot, nameserver);
        }
    }

    @Override
    public void registerMailboxServer(String domain, String address) throws RemoteException,
            AlreadyRegisteredException, InvalidDomainException {
        System.out.printf("@%s: registerMailboxServer called with domain %s%n", componentId, domain);
        // in case of root level nameserver
        if (!domain.contains(".")) {
            System.out.printf("@%s: adding address %s to this nameserver with domain %s%n", componentId, address, domain);

            String result = addresses.putIfAbsent(domain, address);

            // do not allow re-registration of addresses
            if (result != null) {
                System.err.printf(
                        "@%s: error: cannot register addresses, \"%s\" is already registered%n",
                        componentId,
                        address
                );
                throw new AlreadyRegisteredException(String.format(
                        "cannot register address, \"%s\" is already registered",
                        address
                ));
            }
            // in case of nameserver that control a zone within that of another root level nameserver
        } else {
            String[] zones = domain.split("\\.");
            String rootLevelZone = zones[zones.length - 1];
            String zonesWithoutRoot = domain.substring(0, domain.length() - rootLevelZone.length() - 1);
            System.out.printf("@%s: contacting root level nameserver with domain %s%n", componentId, rootLevelZone);

            // attempt registering nameserver recursively
            INameserverRemote next = nameservers.get(rootLevelZone);
            if (next == null) {
                System.err.printf(
                        "@%s: error: cannot register mailbox server, next nameserver hop \"%s\" not known%n",
                        componentId,
                        domain
                );
                throw new InvalidDomainException(String.format(
                        "cannot register mailbox server, next nameserver hop \"%s\" not known",
                        zonesWithoutRoot
                ));
            }

            // make recursive call
            next.registerMailboxServer(zonesWithoutRoot, address);
        }
    }

    @Override
    public INameserverRemote getNameserver(String zone) throws RemoteException {
        System.out.printf("@%s: nameserver for %s requested%n", componentId, zone);
        return nameservers.get(zone);
    }

    @Override
    public String lookup(String username) throws RemoteException {
        System.out.printf("@%s: lookup of %s requested%n", componentId, username);
        return addresses.get(username);
    }

    @Override
    public String lookupDomain(String domainName) throws RemoteException {
        // root_stub being null tells us that this nameserver is not the root server
        if (root_stub != null) {
            return root_stub.lookupDomain(domainName);
        }

        // set up
        String[] zones = domainName.split("\\.");
        INameserverRemote nameserver;
        nameserver = this;

        // query nameservers until the last zones nameserver is reached
        for (int i = zones.length - 1; i > 0; i--) {
            System.out.println(zones[i]);
            System.out.println(nameserver);
            nameserver = nameserver.getNameserver(zones[i]);
            if (nameserver == null) {
                return null;
            }
        }

        return nameserver.lookup(zones[0]);
    }
}
